/*
 * @(#)ParserImpl.java   1.11 2000/08/16
 *
 */

package org.w3c.tidy;

/**
 * HTML Parser implementation (c) 1998-2000 (W3C) MIT, INRIA, Keio University
 * See Tidy.java for the copyright notice. Derived from <a
 * href="http://www.w3.org/People/Raggett/tidy"> HTML Tidy Release 4 Aug
 * 2000</a>
 * 
 * @author Dave Raggett <dsr@w3.org>
 * @author Andy Quick <ac.quick@sympatico.ca> (translation to Java)
 * @version 1.0, 1999/05/22
 * @version 1.0.1, 1999/05/29
 * @version 1.1, 1999/06/18 Java Bean
 * @version 1.2, 1999/07/10 Tidy Release 7 Jul 1999
 * @version 1.3, 1999/07/30 Tidy Release 26 Jul 1999
 * @version 1.4, 1999/09/04 DOM support
 * @version 1.5, 1999/10/23 Tidy Release 27 Sep 1999
 * @version 1.6, 1999/11/01 Tidy Release 22 Oct 1999
 * @version 1.7, 1999/12/06 Tidy Release 30 Nov 1999
 * @version 1.8, 2000/01/22 Tidy Release 13 Jan 2000
 * @version 1.9, 2000/06/03 Tidy Release 30 Apr 2000
 * @version 1.10, 2000/07/22 Tidy Release 8 Jul 2000
 * @version 1.11, 2000/08/16 Tidy Release 4 Aug 2000
 */

public class ParserImpl {

    //private static int SeenBodyEndTag;  /* AQ: moved into lexer structure */

    private static void parseTag(Lexer lexer, Node node, short mode) {
        // Local fix by GLP 2000-12-21.  Need to reset insertspace if this 
        // is both a non-inline and empty tag (base, link, meta, isindex, hr, area).
        // Remove this code once the fix is made in Tidy.

        /******
         * (Original code follows) if ((node.tag.model & Dict.CM_EMPTY) != 0) {
         * lexer.waswhite = false; return; } else if (!((node.tag.model &
         * Dict.CM_INLINE) != 0)) lexer.insertspace = false;
         *******/

        if (!((node.tag.model & Dict.CM_INLINE) != 0))
            lexer.insertspace = false;

        if ((node.tag.model & Dict.CM_EMPTY) != 0) {
            lexer.waswhite = false;
            return;
        }

        if (node.tag.parser == null || node.type == Node.StartEndTag)
            return;

        node.tag.parser.parse(lexer, node, mode);
    }

    private static void moveToHead(Lexer lexer, Node element, Node node) {
        Node head;
        TagTable tt = lexer.configuration.tt;

        if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
            Report.warning(lexer, element, node, Report.TAG_NOT_ALLOWED_IN);

            while (element.tag != tt.tagHtml)
                element = element.parent;

            for (head = element.content; head != null; head = head.next) {
                if (head.tag == tt.tagHead) {
                    Node.insertNodeAtEnd(head, node);
                    break;
                }
            }

            if (node.tag.parser != null)
                parseTag(lexer, node, Lexer.IgnoreWhitespace);
        } else {
            Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
        }
    }

    public static class ParseHTML implements Parser {

        public void parse(Lexer lexer, Node html, short mode) {
            Node node, head;
            Node frameset = null;
            Node noframes = null;

            lexer.configuration.XmlTags = false;
            lexer.seenBodyEndTag = 0;
            TagTable tt = lexer.configuration.tt;

            for (;;) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);

                if (node == null) {
                    node = lexer.inferredTag("head");
                    break;
                }

                if (node.tag == tt.tagHead)
                    break;

                if (node.tag == html.tag && node.type == Node.EndTag) {
                    Report.warning(lexer, html, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(html, node))
                    continue;

                lexer.ungetToken();
                node = lexer.inferredTag("head");
                break;
            }

            head = node;
            Node.insertNodeAtEnd(html, head);
            getParseHead().parse(lexer, head, mode);

            for (;;) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);

                if (node == null) {
                    if (frameset == null) /* create an empty body */
                        node = lexer.inferredTag("body");

                    return;
                }

                /* robustly handle html tags */
                if (node.tag == html.tag) {
                    if (node.type != Node.StartTag && frameset == null)
                        Report.warning(lexer, html, node, Report.DISCARDING_UNEXPECTED);

                    continue;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(html, node))
                    continue;

                /* if frameset document coerce <body> to <noframes> */
                if (node.tag == tt.tagBody) {
                    if (node.type != Node.StartTag) {
                        Report.warning(lexer, html, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (frameset != null) {
                        lexer.ungetToken();

                        if (noframes == null) {
                            noframes = lexer.inferredTag("noframes");
                            Node.insertNodeAtEnd(frameset, noframes);
                            Report.warning(lexer, html, noframes, Report.INSERTING_TAG);
                        }

                        parseTag(lexer, noframes, mode);
                        continue;
                    }

                    break; /* to parse body */
                }

                /* flag an error if we see more than one frameset */
                if (node.tag == tt.tagFrameset) {
                    if (node.type != Node.StartTag) {
                        Report.warning(lexer, html, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (frameset != null)
                        Report.error(lexer, html, node, Report.DUPLICATE_FRAMESET);
                    else
                        frameset = node;

                    Node.insertNodeAtEnd(html, node);
                    parseTag(lexer, node, mode);

                    /*
                     * see if it includes a noframes element so that we can
                     * merge subsequent noframes elements
                     */

                    for (node = frameset.content; node != null; node = node.next) {
                        if (node.tag == tt.tagNoframes)
                            noframes = node;
                    }
                    continue;
                }

                /* if not a frameset document coerce <noframes> to <body> */
                if (node.tag == tt.tagNoframes) {
                    if (node.type != Node.StartTag) {
                        Report.warning(lexer, html, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (frameset == null) {
                        Report.warning(lexer, html, node, Report.DISCARDING_UNEXPECTED);
                        node = lexer.inferredTag("body");
                        break;
                    }

                    if (noframes == null) {
                        noframes = node;
                        Node.insertNodeAtEnd(frameset, noframes);
                    }

                    parseTag(lexer, noframes, mode);
                    continue;
                }

                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    if (node.tag != null && (node.tag.model & Dict.CM_HEAD) != 0) {
                        moveToHead(lexer, html, node);
                        continue;
                    }
                }

                lexer.ungetToken();

                /* insert other content into noframes element */

                if (frameset != null) {
                    if (noframes == null) {
                        noframes = lexer.inferredTag("noframes");
                        Node.insertNodeAtEnd(frameset, noframes);
                    } else
                        Report.warning(lexer, html, node, Report.NOFRAMES_CONTENT);

                    parseTag(lexer, noframes, mode);
                    continue;
                }

                node = lexer.inferredTag("body");
                break;
            }

            /* node must be body */

            Node.insertNodeAtEnd(html, node);
            parseTag(lexer, node, mode);
        }

    };

    public static class ParseHead implements Parser {

        public void parse(Lexer lexer, Node head, short mode) {
            Node node;
            int HasTitle = 0;
            int HasBase = 0;
            TagTable tt = lexer.configuration.tt;

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == head.tag && node.type == Node.EndTag) {
                    head.closed = true;
                    break;
                }

                if (node.type == Node.TextNode) {
                    lexer.ungetToken();
                    break;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(head, node))
                    continue;

                if (node.type == Node.DocTypeTag) {
                    Node.insertDocType(lexer, head, node);
                    continue;
                }

                /* discard unknown tags */
                if (node.tag == null) {
                    Report.warning(lexer, head, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (!((node.tag.model & Dict.CM_HEAD) != 0)) {
                    lexer.ungetToken();
                    break;
                }

                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    if (node.tag == tt.tagTitle) {
                        ++HasTitle;

                        if (HasTitle > 1)
                            Report.warning(lexer, head, node, Report.TOO_MANY_ELEMENTS);
                    } else if (node.tag == tt.tagBase) {
                        ++HasBase;

                        if (HasBase > 1)
                            Report.warning(lexer, head, node, Report.TOO_MANY_ELEMENTS);
                    } else if (node.tag == tt.tagNoscript)
                        Report.warning(lexer, head, node, Report.TAG_NOT_ALLOWED_IN);

                    Node.insertNodeAtEnd(head, node);
                    parseTag(lexer, node, Lexer.IgnoreWhitespace);
                    continue;
                }

                /* discard unexpected text nodes and end tags */
                Report.warning(lexer, head, node, Report.DISCARDING_UNEXPECTED);
            }

            if (HasTitle == 0) {
                Report.warning(lexer, head, null, Report.MISSING_TITLE_ELEMENT);
                Node.insertNodeAtEnd(head, lexer.inferredTag("title"));
            }
        }

    };

    public static class ParseTitle implements Parser {

        public void parse(Lexer lexer, Node title, short mode) {
            Node node;

            while (true) {
                node = lexer.getToken(Lexer.MixedContent);
                if (node == null)
                    break;
                if (node.tag == title.tag && node.type == Node.EndTag) {
                    title.closed = true;
                    Node.trimSpaces(lexer, title);
                    return;
                }

                if (node.type == Node.TextNode) {
                    /* only called for 1st child */
                    if (title.content == null)
                        Node.trimInitialSpace(lexer, title, node);

                    if (node.start >= node.end) {
                        continue;
                    }

                    Node.insertNodeAtEnd(title, node);
                    continue;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(title, node))
                    continue;

                /* discard unknown tags */
                if (node.tag == null) {
                    Report.warning(lexer, title, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* pushback unexpected tokens */
                Report.warning(lexer, title, node, Report.MISSING_ENDTAG_BEFORE);
                lexer.ungetToken();
                Node.trimSpaces(lexer, title);
                return;
            }

            Report.warning(lexer, title, node, Report.MISSING_ENDTAG_FOR);
        }

    };

    public static class ParseScript implements Parser {

        public void parse(Lexer lexer, Node script, short mode) {
            /*
             * This isn't quite right for CDATA content as it recognises tags
             * within the content and parses them accordingly. This will
             * unfortunately screw up scripts which include < + letter, < + !, <
             * + ? or < + / + letter
             */

            Node node;

            node = lexer.getCDATA(script);

            if (node != null)
                Node.insertNodeAtEnd(script, node);
        }

    };

    public static class ParseBody implements Parser {

        public void parse(Lexer lexer, Node body, short mode) {
            Node node;
            boolean checkstack, iswhitenode;

            mode = Lexer.IgnoreWhitespace;
            checkstack = true;
            TagTable tt = lexer.configuration.tt;

            while (true) {
                node = lexer.getToken(mode);
                if (node == null)
                    break;
                if (node.tag == body.tag && node.type == Node.EndTag) {
                    body.closed = true;
                    Node.trimSpaces(lexer, body);
                    lexer.seenBodyEndTag = 1;
                    mode = Lexer.IgnoreWhitespace;

                    if (body.parent.tag == tt.tagNoframes)
                        break;

                    continue;
                }

                if (node.tag == tt.tagNoframes) {
                    if (node.type == Node.StartTag) {
                        Node.insertNodeAtEnd(body, node);
                        getParseBlock().parse(lexer, node, mode);
                        continue;
                    }

                    if (node.type == Node.EndTag && body.parent.tag == tt.tagNoframes) {
                        Node.trimSpaces(lexer, body);
                        lexer.ungetToken();
                        break;
                    }
                }

                if ((node.tag == tt.tagFrame || node.tag == tt.tagFrameset) && body.parent.tag == tt.tagNoframes) {
                    Node.trimSpaces(lexer, body);
                    lexer.ungetToken();
                    break;
                }

                if (node.tag == tt.tagHtml) {
                    if (node.type == Node.StartTag || node.type == Node.StartEndTag)
                        Report.warning(lexer, body, node, Report.DISCARDING_UNEXPECTED);

                    continue;
                }

                iswhitenode = false;

                if (node.type == Node.TextNode && node.end <= node.start + 1
                        && node.textarray[node.start] == (byte) ' ')
                    iswhitenode = true;

                /* deal with comments etc. */
                if (Node.insertMisc(body, node))
                    continue;

                if (lexer.seenBodyEndTag == 1 && !iswhitenode) {
                    ++lexer.seenBodyEndTag;
                    Report.warning(lexer, body, node, Report.CONTENT_AFTER_BODY);
                }

                /* mixed content model permits text */
                if (node.type == Node.TextNode) {
                    if (iswhitenode && mode == Lexer.IgnoreWhitespace) {
                        continue;
                    }

                    if (lexer.configuration.EncloseBodyText && !iswhitenode) {
                        Node para;

                        lexer.ungetToken();
                        para = lexer.inferredTag("p");
                        Node.insertNodeAtEnd(body, para);
                        parseTag(lexer, para, mode);
                        mode = Lexer.MixedContent;
                        continue;
                    } else
                        /* strict doesn't allow text here */
                        lexer.versions &= ~(Dict.VERS_HTML40_STRICT | Dict.VERS_HTML20);

                    if (checkstack) {
                        checkstack = false;

                        if (lexer.inlineDup(node) > 0)
                            continue;
                    }

                    Node.insertNodeAtEnd(body, node);
                    mode = Lexer.MixedContent;
                    continue;
                }

                if (node.type == Node.DocTypeTag) {
                    Node.insertDocType(lexer, body, node);
                    continue;
                }
                /* discard unknown and PARAM tags */
                if (node.tag == null || node.tag == tt.tagParam) {
                    Report.warning(lexer, body, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /*
                 * Netscape allows LI and DD directly in BODY We infer UL or DL
                 * respectively and use this boolean to exclude block-level
                 * elements so as to match Netscape's observed behaviour.
                 */
                lexer.excludeBlocks = false;

                if (!((node.tag.model & Dict.CM_BLOCK) != 0) && !((node.tag.model & Dict.CM_INLINE) != 0)) {
                    /* avoid this error message being issued twice */
                    if (!((node.tag.model & Dict.CM_HEAD) != 0))
                        Report.warning(lexer, body, node, Report.TAG_NOT_ALLOWED_IN);

                    if ((node.tag.model & Dict.CM_HTML) != 0) {
                        /* copy body attributes if current body was inferred */
                        if (node.tag == tt.tagBody && body.implicit && body.attributes == null) {
                            body.attributes = node.attributes;
                            node.attributes = null;
                        }

                        continue;
                    }

                    if ((node.tag.model & Dict.CM_HEAD) != 0) {
                        moveToHead(lexer, body, node);
                        continue;
                    }

                    if ((node.tag.model & Dict.CM_LIST) != 0) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("ul");
                        Node.addClass(node, "noindent");
                        lexer.excludeBlocks = true;
                    } else if ((node.tag.model & Dict.CM_DEFLIST) != 0) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("dl");
                        lexer.excludeBlocks = true;
                    } else if ((node.tag.model & (Dict.CM_TABLE | Dict.CM_ROWGRP | Dict.CM_ROW)) != 0) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("table");
                        lexer.excludeBlocks = true;
                    } else {
                        /*
                         * AQ: The following line is from the official C version
                         * of tidy. It doesn't make sense to me because the '!'
                         * operator has higher precedence than the '&' operator.
                         * It seems to me that the expression always evaluates
                         * to 0. if (!node->tag->model & (CM_ROW | CM_FIELD))
                         * AQ: 13Jan2000 fixed in C tidy
                         */
                        if (!((node.tag.model & (Dict.CM_ROW | Dict.CM_FIELD)) != 0)) {
                            lexer.ungetToken();
                            return;
                        }

                        /* ignore </td> </th> <option> etc. */
                        continue;
                    }
                }

                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagBr)
                        node.type = Node.StartTag;
                    else if (node.tag == tt.tagP) {
                        Node.coerceNode(lexer, node, tt.tagBr);
                        Node.insertNodeAtEnd(body, node);
                        node = lexer.inferredTag("br");
                    } else if ((node.tag.model & Dict.CM_INLINE) != 0)
                        lexer.popInline(node);
                }

                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    if (((node.tag.model & Dict.CM_INLINE) != 0) && !((node.tag.model & Dict.CM_MIXED) != 0)) {
                        /* HTML4 strict doesn't allow inline content here */
                        /* but HTML2 does allow img elements as children of body */
                        if (node.tag == tt.tagImg)
                            lexer.versions &= ~Dict.VERS_HTML40_STRICT;
                        else
                            lexer.versions &= ~(Dict.VERS_HTML40_STRICT | Dict.VERS_HTML20);

                        if (checkstack && !node.implicit) {
                            checkstack = false;

                            if (lexer.inlineDup(node) > 0)
                                continue;
                        }

                        mode = Lexer.MixedContent;
                    } else {
                        checkstack = true;
                        mode = Lexer.IgnoreWhitespace;
                    }

                    if (node.implicit)
                        Report.warning(lexer, body, node, Report.INSERTING_TAG);

                    Node.insertNodeAtEnd(body, node);
                    parseTag(lexer, node, mode);
                    continue;
                }

                /* discard unexpected tags */
                Report.warning(lexer, body, node, Report.DISCARDING_UNEXPECTED);
            }
        }

    };

    public static class ParseFrameSet implements Parser {

        public void parse(Lexer lexer, Node frameset, short mode) {
            Node node;
            TagTable tt = lexer.configuration.tt;

            lexer.badAccess |= Report.USING_FRAMES;

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == frameset.tag && node.type == Node.EndTag) {
                    frameset.closed = true;
                    Node.trimSpaces(lexer, frameset);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(frameset, node))
                    continue;

                if (node.tag == null) {
                    Report.warning(lexer, frameset, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    if (node.tag != null && (node.tag.model & Dict.CM_HEAD) != 0) {
                        moveToHead(lexer, frameset, node);
                        continue;
                    }
                }

                if (node.tag == tt.tagBody) {
                    lexer.ungetToken();
                    node = lexer.inferredTag("noframes");
                    Report.warning(lexer, frameset, node, Report.INSERTING_TAG);
                }

                if (node.type == Node.StartTag && (node.tag.model & Dict.CM_FRAMES) != 0) {
                    Node.insertNodeAtEnd(frameset, node);
                    lexer.excludeBlocks = false;
                    parseTag(lexer, node, Lexer.MixedContent);
                    continue;
                } else if (node.type == Node.StartEndTag && (node.tag.model & Dict.CM_FRAMES) != 0) {
                    Node.insertNodeAtEnd(frameset, node);
                    continue;
                }

                /* discard unexpected tags */
                Report.warning(lexer, frameset, node, Report.DISCARDING_UNEXPECTED);
            }

            Report.warning(lexer, frameset, node, Report.MISSING_ENDTAG_FOR);
        }

    };

    public static class ParseInline implements Parser {

        public void parse(Lexer lexer, Node element, short mode) {
            Node node, parent;
            TagTable tt = lexer.configuration.tt;

            if ((element.tag.model & Dict.CM_EMPTY) != 0)
                return;

            if (element.tag == tt.tagA) {
                if (element.attributes == null) {
                    Report.warning(lexer, element.parent, element, Report.DISCARDING_UNEXPECTED);
                    Node.discardElement(element);
                    return;
                }
            }

            /*
             * ParseInline is used for some block level elements like H1 to H6
             * For such elements we need to insert inline emphasis tags
             * currently on the inline stack. For Inline elements, we normally
             * push them onto the inline stack provided they aren't implicit or
             * OBJECT/APPLET. This test is carried out in PushInline and
             * PopInline, see istack.c We don't push A or SPAN to replicate
             * current browser behavior
             */
            if (((element.tag.model & Dict.CM_BLOCK) != 0) || (element.tag == tt.tagDt))
                lexer.inlineDup(null);
            else if ((element.tag.model & Dict.CM_INLINE) != 0 && element.tag != tt.tagA && element.tag != tt.tagSpan)
                lexer.pushInline(element);

            if (element.tag == tt.tagNobr)
                lexer.badLayout |= Report.USING_NOBR;
            else if (element.tag == tt.tagFont)
                lexer.badLayout |= Report.USING_FONT;

            /* Inline elements may or may not be within a preformatted element */
            if (mode != Lexer.Preformatted)
                mode = Lexer.MixedContent;

            while (true) {
                node = lexer.getToken(mode);
                if (node == null)
                    break;
                /* end tag for current element */
                if (node.tag == element.tag && node.type == Node.EndTag) {
                    if ((element.tag.model & Dict.CM_INLINE) != 0 && element.tag != tt.tagA)
                        lexer.popInline(node);

                    if (!((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, element);
                    /*
                     * if a font element wraps an anchor and nothing else then
                     * move the font element inside the anchor since otherwise
                     * it won't alter the anchor text color
                     */
                    if (element.tag == tt.tagFont && element.content != null && element.content == element.last) {
                        Node child = element.content;

                        if (child.tag == tt.tagA) {
                            child.parent = element.parent;
                            child.next = element.next;
                            child.prev = element.prev;

                            if (child.prev != null)
                                child.prev.next = child;
                            else
                                child.parent.content = child;

                            if (child.next != null)
                                child.next.prev = child;
                            else
                                child.parent.last = child;

                            element.next = null;
                            element.prev = null;
                            element.parent = child;
                            element.content = child.content;
                            element.last = child.last;
                            child.content = element;
                            child.last = element;
                            for (child = element.content; child != null; child = child.next)
                                child.parent = element;
                        }
                    }
                    element.closed = true;
                    Node.trimSpaces(lexer, element);
                    Node.trimEmptyElement(lexer, element);
                    return;
                }

                /* <u>...<u> map 2nd <u> to </u> if 1st is explicit */
                /* otherwise emphasis nesting is probably unintentional */
                /* big and small have cumulative effect to leave them alone */
                if (node.type == Node.StartTag && node.tag == element.tag && lexer.isPushed(node) && !node.implicit
                        && !element.implicit && node.tag != null && ((node.tag.model & Dict.CM_INLINE) != 0)
                        && node.tag != tt.tagA && node.tag != tt.tagFont && node.tag != tt.tagBig
                        && node.tag != tt.tagSmall) {
                    if (element.content != null && node.attributes == null) {
                        Report.warning(lexer, element, node, Report.COERCE_TO_ENDTAG);
                        node.type = Node.EndTag;
                        lexer.ungetToken();
                        continue;
                    }

                    Report.warning(lexer, element, node, Report.NESTED_EMPHASIS);
                }

                if (node.type == Node.TextNode) {
                    /* only called for 1st child */
                    if (element.content == null && !((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, element);

                    if (node.start >= node.end) {
                        continue;
                    }

                    Node.insertNodeAtEnd(element, node);
                    continue;
                }

                /* mixed content model so allow text */
                if (Node.insertMisc(element, node))
                    continue;

                /* deal with HTML tags */
                if (node.tag == tt.tagHtml) {
                    if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                        Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    /* otherwise infer end of inline element */
                    lexer.ungetToken();
                    if (!((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, element);
                    Node.trimEmptyElement(lexer, element);
                    return;
                }

                /* within <dt> or <pre> map <p> to <br> */
                if (node.tag == tt.tagP
                        && node.type == Node.StartTag
                        && ((mode & Lexer.Preformatted) != 0 || element.tag == tt.tagDt || element
                                .isDescendantOf(tt.tagDt))) {
                    node.tag = tt.tagBr;
                    node.element = "br";
                    Node.trimSpaces(lexer, element);
                    Node.insertNodeAtEnd(element, node);
                    continue;
                }

                /* ignore unknown and PARAM tags */
                if (node.tag == null || node.tag == tt.tagParam) {
                    Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (node.tag == tt.tagBr && node.type == Node.EndTag)
                    node.type = Node.StartTag;

                if (node.type == Node.EndTag) {
                    /* coerce </br> to <br> */
                    if (node.tag == tt.tagBr)
                        node.type = Node.StartTag;
                    else if (node.tag == tt.tagP) {
                        /* coerce unmatched </p> to <br><br> */
                        if (!element.isDescendantOf(tt.tagP)) {
                            Node.coerceNode(lexer, node, tt.tagBr);
                            Node.trimSpaces(lexer, element);
                            Node.insertNodeAtEnd(element, node);
                            node = lexer.inferredTag("br");
                            continue;
                        }
                    } else if ((node.tag.model & Dict.CM_INLINE) != 0 && node.tag != tt.tagA
                            && !((node.tag.model & Dict.CM_OBJECT) != 0) && (element.tag.model & Dict.CM_INLINE) != 0) {
                        /* allow any inline end tag to end current element */
                        lexer.popInline(element);

                        if (element.tag != tt.tagA) {
                            if (node.tag == tt.tagA && node.tag != element.tag) {
                                Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);
                                lexer.ungetToken();
                            } else {
                                Report.warning(lexer, element, node, Report.NON_MATCHING_ENDTAG);
                            }

                            if (!((mode & Lexer.Preformatted) != 0))
                                Node.trimSpaces(lexer, element);
                            Node.trimEmptyElement(lexer, element);
                            return;
                        }

                        /*
                         * if parent is <a> then discard unexpected inline end
                         * tag
                         */
                        Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    } /*
                       * special case </tr> etc. for stuff moved in front of
                       * table
                       */
                    else if (lexer.exiled && node.tag.model != 0 && (node.tag.model & Dict.CM_TABLE) != 0) {
                        lexer.ungetToken();
                        Node.trimSpaces(lexer, element);
                        Node.trimEmptyElement(lexer, element);
                        return;
                    }
                }

                /* allow any header tag to end current header */
                if ((node.tag.model & Dict.CM_HEADING) != 0 && (element.tag.model & Dict.CM_HEADING) != 0) {
                    if (node.tag == element.tag) {
                        Report.warning(lexer, element, node, Report.NON_MATCHING_ENDTAG);
                    } else {
                        Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);
                        lexer.ungetToken();
                    }
                    if (!((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, element);
                    Node.trimEmptyElement(lexer, element);
                    return;
                }

                /*
                 * an <A> tag to ends any open <A> element but <A href=...> is
                 * mapped to </A><A href=...>
                 */
                if (node.tag == tt.tagA && !node.implicit && lexer.isPushed(node)) {
                    /* coerce <a> to </a> unless it has some attributes */
                    if (node.attributes == null) {
                        node.type = Node.EndTag;
                        Report.warning(lexer, element, node, Report.COERCE_TO_ENDTAG);
                        lexer.popInline(node);
                        lexer.ungetToken();
                        continue;
                    }

                    lexer.ungetToken();
                    Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);
                    lexer.popInline(element);
                    if (!((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, element);
                    Node.trimEmptyElement(lexer, element);
                    return;
                }

                if ((element.tag.model & Dict.CM_HEADING) != 0) {
                    if (node.tag == tt.tagCenter || node.tag == tt.tagDiv) {
                        if (node.type != Node.StartTag && node.type != Node.StartEndTag) {
                            Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                            continue;
                        }

                        Report.warning(lexer, element, node, Report.TAG_NOT_ALLOWED_IN);

                        /* insert center as parent if heading is empty */
                        if (element.content == null) {
                            Node.insertNodeAsParent(element, node);
                            continue;
                        }

                        /* split heading and make center parent of 2nd part */
                        Node.insertNodeAfterElement(element, node);

                        if (!((mode & Lexer.Preformatted) != 0))
                            Node.trimSpaces(lexer, element);

                        element = lexer.cloneNode(element);
                        element.start = lexer.lexsize;
                        element.end = lexer.lexsize;
                        Node.insertNodeAtEnd(node, element);
                        continue;
                    }

                    if (node.tag == tt.tagHr) {
                        if (node.type != Node.StartTag && node.type != Node.StartEndTag) {
                            Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                            continue;
                        }

                        Report.warning(lexer, element, node, Report.TAG_NOT_ALLOWED_IN);

                        /* insert hr before heading if heading is empty */
                        if (element.content == null) {
                            Node.insertNodeBeforeElement(element, node);
                            continue;
                        }

                        /* split heading and insert hr before 2nd part */
                        Node.insertNodeAfterElement(element, node);

                        if (!((mode & Lexer.Preformatted) != 0))
                            Node.trimSpaces(lexer, element);

                        element = lexer.cloneNode(element);
                        element.start = lexer.lexsize;
                        element.end = lexer.lexsize;
                        Node.insertNodeAfterElement(node, element);
                        continue;
                    }
                }

                if (element.tag == tt.tagDt) {
                    if (node.tag == tt.tagHr) {
                        Node dd;

                        if (node.type != Node.StartTag && node.type != Node.StartEndTag) {
                            Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                            continue;
                        }

                        Report.warning(lexer, element, node, Report.TAG_NOT_ALLOWED_IN);
                        dd = lexer.inferredTag("dd");

                        /* insert hr within dd before dt if dt is empty */
                        if (element.content == null) {
                            Node.insertNodeBeforeElement(element, dd);
                            Node.insertNodeAtEnd(dd, node);
                            continue;
                        }

                        /* split dt and insert hr within dd before 2nd part */
                        Node.insertNodeAfterElement(element, dd);
                        Node.insertNodeAtEnd(dd, node);

                        if (!((mode & Lexer.Preformatted) != 0))
                            Node.trimSpaces(lexer, element);

                        element = lexer.cloneNode(element);
                        element.start = lexer.lexsize;
                        element.end = lexer.lexsize;
                        Node.insertNodeAfterElement(dd, element);
                        continue;
                    }
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    for (parent = element.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            if (!((element.tag.model & Dict.CM_OPT) != 0) && !element.implicit)
                                Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);

                            if (element.tag == tt.tagA)
                                lexer.popInline(element);

                            lexer.ungetToken();

                            if (!((mode & Lexer.Preformatted) != 0))
                                Node.trimSpaces(lexer, element);

                            Node.trimEmptyElement(lexer, element);
                            return;
                        }
                    }
                }

                /* block level tags end this element */
                if (!((node.tag.model & Dict.CM_INLINE) != 0)) {
                    if (node.type != Node.StartTag) {
                        Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (!((element.tag.model & Dict.CM_OPT) != 0))
                        Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);

                    if ((node.tag.model & Dict.CM_HEAD) != 0 && !((node.tag.model & Dict.CM_BLOCK) != 0)) {
                        moveToHead(lexer, element, node);
                        continue;
                    }

                    /*
                     * prevent anchors from propagating into block tags except
                     * for headings h1 to h6
                     */
                    if (element.tag == tt.tagA) {
                        if (node.tag != null && !((node.tag.model & Dict.CM_HEADING) != 0))
                            lexer.popInline(element);
                        else if (!(element.content != null)) {
                            Node.discardElement(element);
                            lexer.ungetToken();
                            return;
                        }
                    }

                    lexer.ungetToken();

                    if (!((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, element);

                    Node.trimEmptyElement(lexer, element);
                    return;
                }

                /* parse inline element */
                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    if (node.implicit)
                        Report.warning(lexer, element, node, Report.INSERTING_TAG);

                    /* trim white space before <br> */
                    if (node.tag == tt.tagBr)
                        Node.trimSpaces(lexer, element);

                    Node.insertNodeAtEnd(element, node);
                    parseTag(lexer, node, mode);
                    continue;
                }

                /* discard unexpected tags */
                Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
            }

            if (!((element.tag.model & Dict.CM_OPT) != 0))
                Report.warning(lexer, element, node, Report.MISSING_ENDTAG_FOR);

            Node.trimEmptyElement(lexer, element);
        }
    };

    public static class ParseList implements Parser {

        public void parse(Lexer lexer, Node list, short mode) {
            Node node;
            Node parent;
            TagTable tt = lexer.configuration.tt;

            if ((list.tag.model & Dict.CM_EMPTY) != 0)
                return;

            lexer.insert = -1; /* defer implicit inline start tags */

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;

                if (node.tag == list.tag && node.type == Node.EndTag) {
                    if ((list.tag.model & Dict.CM_OBSOLETE) != 0)
                        Node.coerceNode(lexer, list, tt.tagUl);

                    list.closed = true;
                    Node.trimEmptyElement(lexer, list);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(list, node))
                    continue;

                if (node.type != Node.TextNode && node.tag == null) {
                    Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (node.tag != null && (node.tag.model & Dict.CM_INLINE) != 0) {
                        Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                        lexer.popInline(node);
                        continue;
                    }

                    for (parent = list.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            Report.warning(lexer, list, node, Report.MISSING_ENDTAG_BEFORE);
                            lexer.ungetToken();

                            if ((list.tag.model & Dict.CM_OBSOLETE) != 0)
                                Node.coerceNode(lexer, list, tt.tagUl);

                            Node.trimEmptyElement(lexer, list);
                            return;
                        }
                    }

                    Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (node.tag != tt.tagLi) {
                    lexer.ungetToken();

                    if (node.tag != null && (node.tag.model & Dict.CM_BLOCK) != 0 && lexer.excludeBlocks) {
                        Report.warning(lexer, list, node, Report.MISSING_ENDTAG_BEFORE);
                        Node.trimEmptyElement(lexer, list);
                        return;
                    }

                    node = lexer.inferredTag("li");
                    node.addAttribute("style", "list-style: none");
                    Report.warning(lexer, list, node, Report.MISSING_STARTTAG);
                }

                /* node should be <LI> */
                Node.insertNodeAtEnd(list, node);
                parseTag(lexer, node, Lexer.IgnoreWhitespace);
            }

            if ((list.tag.model & Dict.CM_OBSOLETE) != 0)
                Node.coerceNode(lexer, list, tt.tagUl);

            Report.warning(lexer, list, node, Report.MISSING_ENDTAG_FOR);
            Node.trimEmptyElement(lexer, list);
        }

    };

    public static class ParseDefList implements Parser {

        public void parse(Lexer lexer, Node list, short mode) {
            Node node, parent;
            TagTable tt = lexer.configuration.tt;

            if ((list.tag.model & Dict.CM_EMPTY) != 0)
                return;

            lexer.insert = -1; /* defer implicit inline start tags */

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == list.tag && node.type == Node.EndTag) {
                    list.closed = true;
                    Node.trimEmptyElement(lexer, list);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(list, node))
                    continue;

                if (node.type == Node.TextNode) {
                    lexer.ungetToken();
                    node = lexer.inferredTag("dt");
                    Report.warning(lexer, list, node, Report.MISSING_STARTTAG);
                }

                if (node.tag == null) {
                    Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    for (parent = list.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            Report.warning(lexer, list, node, Report.MISSING_ENDTAG_BEFORE);

                            lexer.ungetToken();
                            Node.trimEmptyElement(lexer, list);
                            return;
                        }
                    }
                }

                /* center in a dt or a dl breaks the dl list in two */
                if (node.tag == tt.tagCenter) {
                    if (list.content != null)
                        Node.insertNodeAfterElement(list, node);
                    else /* trim empty dl list */
                    {
                        Node.insertNodeBeforeElement(list, node);
                        Node.discardElement(list);
                    }

                    /* and parse contents of center */
                    parseTag(lexer, node, mode);

                    /* now create a new dl element */
                    list = lexer.inferredTag("dl");
                    Node.insertNodeAfterElement(node, list);
                    continue;
                }

                if (!(node.tag == tt.tagDt || node.tag == tt.tagDd)) {
                    lexer.ungetToken();

                    if (!((node.tag.model & (Dict.CM_BLOCK | Dict.CM_INLINE)) != 0)) {
                        Report.warning(lexer, list, node, Report.TAG_NOT_ALLOWED_IN);
                        Node.trimEmptyElement(lexer, list);
                        return;
                    }

                    /* if DD appeared directly in BODY then exclude blocks */
                    if (!((node.tag.model & Dict.CM_INLINE) != 0) && lexer.excludeBlocks) {
                        Node.trimEmptyElement(lexer, list);
                        return;
                    }

                    node = lexer.inferredTag("dd");
                    Report.warning(lexer, list, node, Report.MISSING_STARTTAG);
                }

                if (node.type == Node.EndTag) {
                    Report.warning(lexer, list, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* node should be <DT> or <DD> */
                Node.insertNodeAtEnd(list, node);
                parseTag(lexer, node, Lexer.IgnoreWhitespace);
            }

            Report.warning(lexer, list, node, Report.MISSING_ENDTAG_FOR);
            Node.trimEmptyElement(lexer, list);
        }

    };

    public static class ParsePre implements Parser {

        public void parse(Lexer lexer, Node pre, short mode) {
            Node node, parent;
            TagTable tt = lexer.configuration.tt;

            if ((pre.tag.model & Dict.CM_EMPTY) != 0)
                return;

            if ((pre.tag.model & Dict.CM_OBSOLETE) != 0)
                Node.coerceNode(lexer, pre, tt.tagPre);

            lexer.inlineDup(null); /* tell lexer to insert inlines if needed */

            while (true) {
                node = lexer.getToken(Lexer.Preformatted);
                if (node == null)
                    break;
                if (node.tag == pre.tag && node.type == Node.EndTag) {
                    Node.trimSpaces(lexer, pre);
                    pre.closed = true;
                    Node.trimEmptyElement(lexer, pre);
                    return;
                }

                if (node.tag == tt.tagHtml) {
                    if (node.type == Node.StartTag || node.type == Node.StartEndTag)
                        Report.warning(lexer, pre, node, Report.DISCARDING_UNEXPECTED);

                    continue;
                }

                if (node.type == Node.TextNode) {
                    /* if first check for inital newline */
                    if (pre.content == null) {
                        if (node.textarray[node.start] == (byte) '\n')
                            ++node.start;

                        if (node.start >= node.end) {
                            continue;
                        }
                    }

                    Node.insertNodeAtEnd(pre, node);
                    continue;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(pre, node))
                    continue;

                /* discard unknown and PARAM tags */
                if (node.tag == null || node.tag == tt.tagParam) {
                    Report.warning(lexer, pre, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (node.tag == tt.tagP) {
                    if (node.type == Node.StartTag) {
                        Report.warning(lexer, pre, node, Report.USING_BR_INPLACE_OF);

                        /* trim white space before <p> in <pre> */
                        Node.trimSpaces(lexer, pre);

                        /* coerce both <p> and </p> to <br> */
                        Node.coerceNode(lexer, node, tt.tagBr);
                        Node.insertNodeAtEnd(pre, node);
                    } else {
                        Report.warning(lexer, pre, node, Report.DISCARDING_UNEXPECTED);
                    }
                    continue;
                }

                if ((node.tag.model & Dict.CM_HEAD) != 0 && !((node.tag.model & Dict.CM_BLOCK) != 0)) {
                    moveToHead(lexer, pre, node);
                    continue;
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, pre, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    for (parent = pre.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            Report.warning(lexer, pre, node, Report.MISSING_ENDTAG_BEFORE);

                            lexer.ungetToken();
                            Node.trimSpaces(lexer, pre);
                            Node.trimEmptyElement(lexer, pre);
                            return;
                        }
                    }
                }

                /* what about head content, HEAD, BODY tags etc? */
                if (!((node.tag.model & Dict.CM_INLINE) != 0)) {
                    if (node.type != Node.StartTag) {
                        Report.warning(lexer, pre, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    Report.warning(lexer, pre, node, Report.MISSING_ENDTAG_BEFORE);
                    lexer.excludeBlocks = true;

                    /* check if we need to infer a container */
                    if ((node.tag.model & Dict.CM_LIST) != 0) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("ul");
                        Node.addClass(node, "noindent");
                    } else if ((node.tag.model & Dict.CM_DEFLIST) != 0) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("dl");
                    } else if ((node.tag.model & Dict.CM_TABLE) != 0) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("table");
                    }

                    Node.insertNodeAfterElement(pre, node);
                    pre = lexer.inferredTag("pre");
                    Node.insertNodeAfterElement(node, pre);
                    parseTag(lexer, node, Lexer.IgnoreWhitespace);
                    lexer.excludeBlocks = false;
                    continue;
                }
                /*
                 * if (!((node.tag.model & Dict.CM_INLINE) != 0)) {
                 * Report.warning(lexer, pre, node,
                 * Report.MISSING_ENDTAG_BEFORE); lexer.ungetToken(); return; }
                 */
                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    /* trim white space before <br> */
                    if (node.tag == tt.tagBr)
                        Node.trimSpaces(lexer, pre);

                    Node.insertNodeAtEnd(pre, node);
                    parseTag(lexer, node, Lexer.Preformatted);
                    continue;
                }

                /* discard unexpected tags */
                Report.warning(lexer, pre, node, Report.DISCARDING_UNEXPECTED);
            }

            Report.warning(lexer, pre, node, Report.MISSING_ENDTAG_FOR);
            Node.trimEmptyElement(lexer, pre);
        }

    };

    public static class ParseBlock implements Parser {

        public void parse(Lexer lexer, Node element, short mode)
        /*
         * element is node created by the lexer upon seeing the start tag, or by
         * the parser when the start tag is inferred
         */
        {
            Node node, parent;
            boolean checkstack;
            int istackbase = 0;
            TagTable tt = lexer.configuration.tt;

            checkstack = true;

            if ((element.tag.model & Dict.CM_EMPTY) != 0)
                return;

            if (element.tag == tt.tagForm && element.isDescendantOf(tt.tagForm))
                Report.warning(lexer, element, null, Report.ILLEGAL_NESTING);

            /*
             * InlineDup() asks the lexer to insert inline emphasis tags
             * currently pushed on the istack, but take care to avoid
             * propagating inline emphasis inside OBJECT or APPLET. For these
             * elements a fresh inline stack context is created and disposed of
             * upon reaching the end of the element. They thus behave like table
             * cells in this respect.
             */
            if ((element.tag.model & Dict.CM_OBJECT) != 0) {
                istackbase = lexer.istackbase;
                lexer.istackbase = lexer.istack.size();
            }

            if (!((element.tag.model & Dict.CM_MIXED) != 0))
                lexer.inlineDup(null);

            mode = Lexer.IgnoreWhitespace;

            while (true) {
                node = lexer.getToken(mode /* Lexer.MixedContent */);
                if (node == null)
                    break;
                /* end tag for this element */
                if (node.type == Node.EndTag && node.tag != null
                        && (node.tag == element.tag || element.was == node.tag)) {

                    if ((element.tag.model & Dict.CM_OBJECT) != 0) {
                        /* pop inline stack */
                        while (lexer.istack.size() > lexer.istackbase)
                            lexer.popInline(null);
                        lexer.istackbase = istackbase;
                    }

                    element.closed = true;
                    Node.trimSpaces(lexer, element);
                    Node.trimEmptyElement(lexer, element);
                    return;
                }

                if (node.tag == tt.tagHtml || node.tag == tt.tagHead || node.tag == tt.tagBody) {
                    if (node.type == Node.StartTag || node.type == Node.StartEndTag)
                        Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);

                    continue;
                }

                if (node.type == Node.EndTag) {
                    if (node.tag == null) {
                        Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);

                        continue;
                    } else if (node.tag == tt.tagBr)
                        node.type = Node.StartTag;
                    else if (node.tag == tt.tagP) {
                        Node.coerceNode(lexer, node, tt.tagBr);
                        Node.insertNodeAtEnd(element, node);
                        node = lexer.inferredTag("br");
                    } else {
                        /*
                         * if this is the end tag for an ancestor element then
                         * infer end tag for this element
                         */
                        for (parent = element.parent; parent != null; parent = parent.parent) {
                            if (node.tag == parent.tag) {
                                if (!((element.tag.model & Dict.CM_OPT) != 0))
                                    Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);

                                lexer.ungetToken();

                                if ((element.tag.model & Dict.CM_OBJECT) != 0) {
                                    /* pop inline stack */
                                    while (lexer.istack.size() > lexer.istackbase)
                                        lexer.popInline(null);
                                    lexer.istackbase = istackbase;
                                }

                                Node.trimSpaces(lexer, element);
                                Node.trimEmptyElement(lexer, element);
                                return;
                            }
                        }
                        /*
                         * special case </tr> etc. for stuff moved in front of
                         * table
                         */
                        if (lexer.exiled && node.tag.model != 0 && (node.tag.model & Dict.CM_TABLE) != 0) {
                            lexer.ungetToken();
                            Node.trimSpaces(lexer, element);
                            Node.trimEmptyElement(lexer, element);
                            return;
                        }
                    }
                }

                /* mixed content model permits text */
                if (node.type == Node.TextNode) {
                    boolean iswhitenode = false;

                    if (node.type == Node.TextNode && node.end <= node.start + 1
                            && lexer.lexbuf[node.start] == (byte) ' ')
                        iswhitenode = true;

                    if (lexer.configuration.EncloseBlockText && !iswhitenode) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("p");
                        Node.insertNodeAtEnd(element, node);
                        parseTag(lexer, node, Lexer.MixedContent);
                        continue;
                    }

                    if (checkstack) {
                        checkstack = false;

                        if (!((element.tag.model & Dict.CM_MIXED) != 0)) {
                            if (lexer.inlineDup(node) > 0)
                                continue;
                        }
                    }

                    Node.insertNodeAtEnd(element, node);
                    mode = Lexer.MixedContent;
                    /*
                     * HTML4 strict doesn't allow mixed content for elements
                     * with %block; as their content model
                     */
                    lexer.versions &= ~Dict.VERS_HTML40_STRICT;
                    continue;
                }

                if (Node.insertMisc(element, node))
                    continue;

                /* allow PARAM elements? */
                if (node.tag == tt.tagParam) {
                    if (((element.tag.model & Dict.CM_PARAM) != 0)
                            && (node.type == Node.StartTag || node.type == Node.StartEndTag)) {
                        Node.insertNodeAtEnd(element, node);
                        continue;
                    }

                    /* otherwise discard it */
                    Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* allow AREA elements? */
                if (node.tag == tt.tagArea) {
                    if ((element.tag == tt.tagMap) && (node.type == Node.StartTag || node.type == Node.StartEndTag)) {
                        Node.insertNodeAtEnd(element, node);
                        continue;
                    }

                    /* otherwise discard it */
                    Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* ignore unknown start/end tags */
                if (node.tag == null) {
                    Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /*
                 * Allow Dict.CM_INLINE elements here. Allow Dict.CM_BLOCK
                 * elements here unless lexer.excludeBlocks is yes. LI and DD
                 * are special cased. Otherwise infer end tag for this element.
                 */

                if (!((node.tag.model & Dict.CM_INLINE) != 0)) {
                    if (node.type != Node.StartTag && node.type != Node.StartEndTag) {
                        Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (element.tag == tt.tagTd || element.tag == tt.tagTh) {
                        /*
                         * if parent is a table cell, avoid inferring the end of
                         * the cell
                         */

                        if ((node.tag.model & Dict.CM_HEAD) != 0) {
                            moveToHead(lexer, element, node);
                            continue;
                        }

                        if ((node.tag.model & Dict.CM_LIST) != 0) {
                            lexer.ungetToken();
                            node = lexer.inferredTag("ul");
                            Node.addClass(node, "noindent");
                            lexer.excludeBlocks = true;
                        } else if ((node.tag.model & Dict.CM_DEFLIST) != 0) {
                            lexer.ungetToken();
                            node = lexer.inferredTag("dl");
                            lexer.excludeBlocks = true;
                        }

                        /* infer end of current table cell */
                        if (!((node.tag.model & Dict.CM_BLOCK) != 0)) {
                            lexer.ungetToken();
                            Node.trimSpaces(lexer, element);
                            Node.trimEmptyElement(lexer, element);
                            return;
                        }
                    } else if ((node.tag.model & Dict.CM_BLOCK) != 0) {
                        if (lexer.excludeBlocks) {
                            if (!((element.tag.model & Dict.CM_OPT) != 0))
                                Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);

                            lexer.ungetToken();

                            if ((element.tag.model & Dict.CM_OBJECT) != 0)
                                lexer.istackbase = istackbase;

                            Node.trimSpaces(lexer, element);
                            Node.trimEmptyElement(lexer, element);
                            return;
                        }
                    } else /* things like list items */
                    {
                        if (!((element.tag.model & Dict.CM_OPT) != 0) && !element.implicit)
                            Report.warning(lexer, element, node, Report.MISSING_ENDTAG_BEFORE);

                        if ((node.tag.model & Dict.CM_HEAD) != 0) {
                            moveToHead(lexer, element, node);
                            continue;
                        }

                        lexer.ungetToken();

                        if ((node.tag.model & Dict.CM_LIST) != 0) {
                            if (element.parent != null && element.parent.tag != null
                                    && element.parent.tag.parser == getParseList()) {
                                Node.trimSpaces(lexer, element);
                                Node.trimEmptyElement(lexer, element);
                                return;
                            }

                            node = lexer.inferredTag("ul");
                            Node.addClass(node, "noindent");
                        } else if ((node.tag.model & Dict.CM_DEFLIST) != 0) {
                            if (element.parent.tag == tt.tagDl) {
                                Node.trimSpaces(lexer, element);
                                Node.trimEmptyElement(lexer, element);
                                return;
                            }

                            node = lexer.inferredTag("dl");
                        } else if ((node.tag.model & Dict.CM_TABLE) != 0 || (node.tag.model & Dict.CM_ROW) != 0) {
                            node = lexer.inferredTag("table");
                        } else if ((element.tag.model & Dict.CM_OBJECT) != 0) {
                            /* pop inline stack */
                            while (lexer.istack.size() > lexer.istackbase)
                                lexer.popInline(null);
                            lexer.istackbase = istackbase;
                            Node.trimSpaces(lexer, element);
                            Node.trimEmptyElement(lexer, element);
                            return;

                        } else {
                            Node.trimSpaces(lexer, element);
                            Node.trimEmptyElement(lexer, element);
                            return;
                        }
                    }
                }

                /* parse known element */
                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    if ((node.tag.model & Dict.CM_INLINE) != 0) {
                        if (checkstack && !node.implicit) {
                            checkstack = false;

                            if (lexer.inlineDup(node) > 0)
                                continue;
                        }

                        mode = Lexer.MixedContent;
                    } else {
                        checkstack = true;
                        mode = Lexer.IgnoreWhitespace;
                    }

                    /* trim white space before <br> */
                    if (node.tag == tt.tagBr)
                        Node.trimSpaces(lexer, element);

                    Node.insertNodeAtEnd(element, node);

                    if (node.implicit)
                        Report.warning(lexer, element, node, Report.INSERTING_TAG);

                    parseTag(lexer, node, Lexer.IgnoreWhitespace /*
                                                                  * Lexer.MixedContent
                                                                  */);
                    continue;
                }

                /* discard unexpected tags */
                if (node.type == Node.EndTag)
                    lexer.popInline(node); /* if inline end tag */

                Report.warning(lexer, element, node, Report.DISCARDING_UNEXPECTED);
            }

            if (!((element.tag.model & Dict.CM_OPT) != 0))
                Report.warning(lexer, element, node, Report.MISSING_ENDTAG_FOR);

            if ((element.tag.model & Dict.CM_OBJECT) != 0) {
                /* pop inline stack */
                while (lexer.istack.size() > lexer.istackbase)
                    lexer.popInline(null);
                lexer.istackbase = istackbase;
            }

            Node.trimSpaces(lexer, element);
            Node.trimEmptyElement(lexer, element);
        }

    };

    public static class ParseTableTag implements Parser {

        public void parse(Lexer lexer, Node table, short mode) {
            Node node, parent;
            int istackbase;
            TagTable tt = lexer.configuration.tt;

            lexer.deferDup();
            istackbase = lexer.istackbase;
            lexer.istackbase = lexer.istack.size();

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == table.tag && node.type == Node.EndTag) {
                    lexer.istackbase = istackbase;
                    table.closed = true;
                    Node.trimEmptyElement(lexer, table);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(table, node))
                    continue;

                /* discard unknown tags */
                if (node.tag == null && node.type != Node.TextNode) {
                    Report.warning(lexer, table, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* if TD or TH or text or inline or block then infer <TR> */

                if (node.type != Node.EndTag) {
                    if (node.tag == tt.tagTd || node.tag == tt.tagTh || node.tag == tt.tagTable) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("tr");
                        Report.warning(lexer, table, node, Report.MISSING_STARTTAG);
                    } else if (node.type == Node.TextNode || (node.tag.model & (Dict.CM_BLOCK | Dict.CM_INLINE)) != 0) {
                        Node.insertNodeBeforeElement(table, node);
                        Report.warning(lexer, table, node, Report.TAG_NOT_ALLOWED_IN);
                        lexer.exiled = true;

                        /*
                         * AQ: TODO Line 2040 of parser.c (13 Jan 2000) reads as
                         * follows: if (!node->type == TextNode) This will
                         * always evaluate to false. This has been reported to
                         * Dave Raggett <dsr@w3.org>
                         */
                        //Should be?: if (!(node.type == Node.TextNode))
                        if (false)
                            parseTag(lexer, node, Lexer.IgnoreWhitespace);

                        lexer.exiled = false;
                        continue;
                    } else if ((node.tag.model & Dict.CM_HEAD) != 0) {
                        moveToHead(lexer, table, node);
                        continue;
                    }
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, table, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (node.tag != null && (node.tag.model & (Dict.CM_TABLE | Dict.CM_ROW)) != 0) {
                        Report.warning(lexer, table, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    for (parent = table.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            Report.warning(lexer, table, node, Report.MISSING_ENDTAG_BEFORE);
                            lexer.ungetToken();
                            lexer.istackbase = istackbase;
                            Node.trimEmptyElement(lexer, table);
                            return;
                        }
                    }
                }

                if (!((node.tag.model & Dict.CM_TABLE) != 0)) {
                    lexer.ungetToken();
                    Report.warning(lexer, table, node, Report.TAG_NOT_ALLOWED_IN);
                    lexer.istackbase = istackbase;
                    Node.trimEmptyElement(lexer, table);
                    return;
                }

                if (node.type == Node.StartTag || node.type == Node.StartEndTag) {
                    Node.insertNodeAtEnd(table, node);
                    ;
                    parseTag(lexer, node, Lexer.IgnoreWhitespace);
                    continue;
                }

                /* discard unexpected text nodes and end tags */
                Report.warning(lexer, table, node, Report.DISCARDING_UNEXPECTED);
            }

            Report.warning(lexer, table, node, Report.MISSING_ENDTAG_FOR);
            Node.trimEmptyElement(lexer, table);
            lexer.istackbase = istackbase;
        }

    };

    public static class ParseColGroup implements Parser {

        public void parse(Lexer lexer, Node colgroup, short mode) {
            Node node, parent;
            TagTable tt = lexer.configuration.tt;

            if ((colgroup.tag.model & Dict.CM_EMPTY) != 0)
                return;

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == colgroup.tag && node.type == Node.EndTag) {
                    colgroup.closed = true;
                    return;
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, colgroup, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    for (parent = colgroup.parent; parent != null; parent = parent.parent) {

                        if (node.tag == parent.tag) {
                            lexer.ungetToken();
                            return;
                        }
                    }
                }

                if (node.type == Node.TextNode) {
                    lexer.ungetToken();
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(colgroup, node))
                    continue;

                /* discard unknown tags */
                if (node.tag == null) {
                    Report.warning(lexer, colgroup, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (node.tag != tt.tagCol) {
                    lexer.ungetToken();
                    return;
                }

                if (node.type == Node.EndTag) {
                    Report.warning(lexer, colgroup, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* node should be <COL> */
                Node.insertNodeAtEnd(colgroup, node);
                parseTag(lexer, node, Lexer.IgnoreWhitespace);
            }
        }

    };

    public static class ParseRowGroup implements Parser {

        public void parse(Lexer lexer, Node rowgroup, short mode) {
            Node node, parent;
            TagTable tt = lexer.configuration.tt;

            if ((rowgroup.tag.model & Dict.CM_EMPTY) != 0)
                return;

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == rowgroup.tag) {
                    if (node.type == Node.EndTag) {
                        rowgroup.closed = true;
                        Node.trimEmptyElement(lexer, rowgroup);
                        return;
                    }

                    lexer.ungetToken();
                    return;
                }

                /* if </table> infer end tag */
                if (node.tag == tt.tagTable && node.type == Node.EndTag) {
                    lexer.ungetToken();
                    Node.trimEmptyElement(lexer, rowgroup);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(rowgroup, node))
                    continue;

                /* discard unknown tags */
                if (node.tag == null && node.type != Node.TextNode) {
                    Report.warning(lexer, rowgroup, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /*
                 * if TD or TH then infer <TR> if text or inline or block move
                 * before table if head content move to head
                 */

                if (node.type != Node.EndTag) {
                    if (node.tag == tt.tagTd || node.tag == tt.tagTh) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("tr");
                        Report.warning(lexer, rowgroup, node, Report.MISSING_STARTTAG);
                    } else if (node.type == Node.TextNode || (node.tag.model & (Dict.CM_BLOCK | Dict.CM_INLINE)) != 0) {
                        Node.moveBeforeTable(rowgroup, node, tt);
                        Report.warning(lexer, rowgroup, node, Report.TAG_NOT_ALLOWED_IN);
                        lexer.exiled = true;

                        if (node.type != Node.TextNode)
                            parseTag(lexer, node, Lexer.IgnoreWhitespace);

                        lexer.exiled = false;
                        continue;
                    } else if ((node.tag.model & Dict.CM_HEAD) != 0) {
                        Report.warning(lexer, rowgroup, node, Report.TAG_NOT_ALLOWED_IN);
                        moveToHead(lexer, rowgroup, node);
                        continue;
                    }
                }

                /*
                 * if this is the end tag for ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, rowgroup, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (node.tag == tt.tagTr || node.tag == tt.tagTd || node.tag == tt.tagTh) {
                        Report.warning(lexer, rowgroup, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    for (parent = rowgroup.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            lexer.ungetToken();
                            Node.trimEmptyElement(lexer, rowgroup);
                            return;
                        }
                    }
                }

                /*
                 * if THEAD, TFOOT or TBODY then implied end tag
                 */
                if ((node.tag.model & Dict.CM_ROWGRP) != 0) {
                    if (node.type != Node.EndTag)
                        lexer.ungetToken();

                    Node.trimEmptyElement(lexer, rowgroup);
                    return;
                }

                if (node.type == Node.EndTag) {
                    Report.warning(lexer, rowgroup, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                if (!(node.tag == tt.tagTr)) {
                    node = lexer.inferredTag("tr");
                    Report.warning(lexer, rowgroup, node, Report.MISSING_STARTTAG);
                    lexer.ungetToken();
                }

                /* node should be <TR> */
                Node.insertNodeAtEnd(rowgroup, node);
                parseTag(lexer, node, Lexer.IgnoreWhitespace);
            }

            Node.trimEmptyElement(lexer, rowgroup);
        }

    };

    public static class ParseRow implements Parser {

        public void parse(Lexer lexer, Node row, short mode) {
            Node node, parent;
            boolean exclude_state;
            TagTable tt = lexer.configuration.tt;

            if ((row.tag.model & Dict.CM_EMPTY) != 0)
                return;

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == row.tag) {
                    if (node.type == Node.EndTag) {
                        row.closed = true;
                        Node.fixEmptyRow(lexer, row);
                        return;
                    }

                    lexer.ungetToken();
                    Node.fixEmptyRow(lexer, row);
                    return;
                }

                /*
                 * if this is the end tag for an ancestor element then infer end
                 * tag for this element
                 */
                if (node.type == Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.badForm = 1;
                        Report.warning(lexer, row, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    if (node.tag == tt.tagTd || node.tag == tt.tagTh) {
                        Report.warning(lexer, row, node, Report.DISCARDING_UNEXPECTED);
                        continue;
                    }

                    for (parent = row.parent; parent != null; parent = parent.parent) {
                        if (node.tag == parent.tag) {
                            lexer.ungetToken();
                            Node.trimEmptyElement(lexer, row);
                            return;
                        }
                    }
                }

                /* deal with comments etc. */
                if (Node.insertMisc(row, node))
                    continue;

                /* discard unknown tags */
                if (node.tag == null && node.type != Node.TextNode) {
                    Report.warning(lexer, row, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* discard unexpected <table> element */
                if (node.tag == tt.tagTable) {
                    Report.warning(lexer, row, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* THEAD, TFOOT or TBODY */
                if (node.tag != null && (node.tag.model & Dict.CM_ROWGRP) != 0) {
                    lexer.ungetToken();
                    Node.trimEmptyElement(lexer, row);
                    return;
                }

                if (node.type == Node.EndTag) {
                    Report.warning(lexer, row, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /*
                 * if text or inline or block move before table if head content
                 * move to head
                 */

                if (node.type != Node.EndTag) {
                    if (node.tag == tt.tagForm) {
                        lexer.ungetToken();
                        node = lexer.inferredTag("td");
                        Report.warning(lexer, row, node, Report.MISSING_STARTTAG);
                    } else if (node.type == Node.TextNode || (node.tag.model & (Dict.CM_BLOCK | Dict.CM_INLINE)) != 0) {
                        Node.moveBeforeTable(row, node, tt);
                        Report.warning(lexer, row, node, Report.TAG_NOT_ALLOWED_IN);
                        lexer.exiled = true;

                        if (node.type != Node.TextNode)
                            parseTag(lexer, node, Lexer.IgnoreWhitespace);

                        lexer.exiled = false;
                        continue;
                    } else if ((node.tag.model & Dict.CM_HEAD) != 0) {
                        Report.warning(lexer, row, node, Report.TAG_NOT_ALLOWED_IN);
                        moveToHead(lexer, row, node);
                        continue;
                    }
                }

                if (!(node.tag == tt.tagTd || node.tag == tt.tagTh)) {
                    Report.warning(lexer, row, node, Report.TAG_NOT_ALLOWED_IN);
                    continue;
                }

                /* node should be <TD> or <TH> */
                Node.insertNodeAtEnd(row, node);
                exclude_state = lexer.excludeBlocks;
                lexer.excludeBlocks = false;
                parseTag(lexer, node, Lexer.IgnoreWhitespace);
                lexer.excludeBlocks = exclude_state;

                /* pop inline stack */

                while (lexer.istack.size() > lexer.istackbase)
                    lexer.popInline(null);
            }

            Node.trimEmptyElement(lexer, row);
        }

    };

    public static class ParseNoFrames implements Parser {

        public void parse(Lexer lexer, Node noframes, short mode) {
            Node node;
            boolean checkstack;
            TagTable tt = lexer.configuration.tt;

            lexer.badAccess |= Report.USING_NOFRAMES;
            mode = Lexer.IgnoreWhitespace;
            checkstack = true;

            while (true) {
                node = lexer.getToken(mode);
                if (node == null)
                    break;
                if (node.tag == noframes.tag && node.type == Node.EndTag) {
                    noframes.closed = true;
                    Node.trimSpaces(lexer, noframes);
                    return;
                }

                if ((node.tag == tt.tagFrame || node.tag == tt.tagFrameset)) {
                    Report.warning(lexer, noframes, node, Report.MISSING_ENDTAG_BEFORE);
                    Node.trimSpaces(lexer, noframes);
                    lexer.ungetToken();
                    return;
                }

                if (node.tag == tt.tagHtml) {
                    if (node.type == Node.StartTag || node.type == Node.StartEndTag)
                        Report.warning(lexer, noframes, node, Report.DISCARDING_UNEXPECTED);

                    continue;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(noframes, node))
                    continue;

                if (node.tag == tt.tagBody && node.type == Node.StartTag) {
                    Node.insertNodeAtEnd(noframes, node);
                    parseTag(lexer, node, Lexer.IgnoreWhitespace /* MixedContent */);
                    continue;
                }

                /* implicit body element inferred */
                if (node.type == Node.TextNode || node.tag != null) {
                    lexer.ungetToken();
                    node = lexer.inferredTag("body");
                    if (lexer.configuration.XmlOut)
                        Report.warning(lexer, noframes, node, Report.INSERTING_TAG);
                    Node.insertNodeAtEnd(noframes, node);
                    parseTag(lexer, node, Lexer.IgnoreWhitespace /* MixedContent */);
                    continue;
                }
                /* discard unexpected end tags */
                Report.warning(lexer, noframes, node, Report.DISCARDING_UNEXPECTED);
            }

            Report.warning(lexer, noframes, node, Report.MISSING_ENDTAG_FOR);
        }

    };

    public static class ParseSelect implements Parser {

        public void parse(Lexer lexer, Node field, short mode) {
            Node node;
            TagTable tt = lexer.configuration.tt;

            lexer.insert = -1; /* defer implicit inline start tags */

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == field.tag && node.type == Node.EndTag) {
                    field.closed = true;
                    Node.trimSpaces(lexer, field);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(field, node))
                    continue;

                if (node.type == Node.StartTag
                        && (node.tag == tt.tagOption || node.tag == tt.tagOptgroup || node.tag == tt.tagScript)) {
                    Node.insertNodeAtEnd(field, node);
                    parseTag(lexer, node, Lexer.IgnoreWhitespace);
                    continue;
                }

                /* discard unexpected tags */
                Report.warning(lexer, field, node, Report.DISCARDING_UNEXPECTED);
            }

            Report.warning(lexer, field, node, Report.MISSING_ENDTAG_FOR);
        }

    };

    public static class ParseText implements Parser {

        public void parse(Lexer lexer, Node field, short mode) {
            Node node;
            TagTable tt = lexer.configuration.tt;

            lexer.insert = -1; /* defer implicit inline start tags */

            if (field.tag == tt.tagTextarea)
                mode = Lexer.Preformatted;

            while (true) {
                node = lexer.getToken(mode);
                if (node == null)
                    break;
                if (node.tag == field.tag && node.type == Node.EndTag) {
                    field.closed = true;
                    Node.trimSpaces(lexer, field);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(field, node))
                    continue;

                if (node.type == Node.TextNode) {
                    /* only called for 1st child */
                    if (field.content == null && !((mode & Lexer.Preformatted) != 0))
                        Node.trimSpaces(lexer, field);

                    if (node.start >= node.end) {
                        continue;
                    }

                    Node.insertNodeAtEnd(field, node);
                    continue;
                }

                if (node.tag == tt.tagFont) {
                    Report.warning(lexer, field, node, Report.DISCARDING_UNEXPECTED);
                    continue;
                }

                /* terminate element on other tags */
                if (!((field.tag.model & Dict.CM_OPT) != 0))
                    Report.warning(lexer, field, node, Report.MISSING_ENDTAG_BEFORE);

                lexer.ungetToken();
                Node.trimSpaces(lexer, field);
                return;
            }

            if (!((field.tag.model & Dict.CM_OPT) != 0))
                Report.warning(lexer, field, node, Report.MISSING_ENDTAG_FOR);
        }

    };

    public static class ParseOptGroup implements Parser {

        public void parse(Lexer lexer, Node field, short mode) {
            Node node;
            TagTable tt = lexer.configuration.tt;

            lexer.insert = -1; /* defer implicit inline start tags */

            while (true) {
                node = lexer.getToken(Lexer.IgnoreWhitespace);
                if (node == null)
                    break;
                if (node.tag == field.tag && node.type == Node.EndTag) {
                    field.closed = true;
                    Node.trimSpaces(lexer, field);
                    return;
                }

                /* deal with comments etc. */
                if (Node.insertMisc(field, node))
                    continue;

                if (node.type == Node.StartTag && (node.tag == tt.tagOption || node.tag == tt.tagOptgroup)) {
                    if (node.tag == tt.tagOptgroup)
                        Report.warning(lexer, field, node, Report.CANT_BE_NESTED);

                    Node.insertNodeAtEnd(field, node);
                    parseTag(lexer, node, Lexer.MixedContent);
                    continue;
                }

                /* discard unexpected tags */
                Report.warning(lexer, field, node, Report.DISCARDING_UNEXPECTED);
            }
        }

    };

    public static Parser getParseHTML() {
        return _parseHTML;
    }

    public static Parser getParseHead() {
        return _parseHead;
    }

    public static Parser getParseTitle() {
        return _parseTitle;
    }

    public static Parser getParseScript() {
        return _parseScript;
    }

    public static Parser getParseBody() {
        return _parseBody;
    }

    public static Parser getParseFrameSet() {
        return _parseFrameSet;
    }

    public static Parser getParseInline() {
        return _parseInline;
    }

    public static Parser getParseList() {
        return _parseList;
    }

    public static Parser getParseDefList() {
        return _parseDefList;
    }

    public static Parser getParsePre() {
        return _parsePre;
    }

    public static Parser getParseBlock() {
        return _parseBlock;
    }

    public static Parser getParseTableTag() {
        return _parseTableTag;
    }

    public static Parser getParseColGroup() {
        return _parseColGroup;
    }

    public static Parser getParseRowGroup() {
        return _parseRowGroup;
    }

    public static Parser getParseRow() {
        return _parseRow;
    }

    public static Parser getParseNoFrames() {
        return _parseNoFrames;
    }

    public static Parser getParseSelect() {
        return _parseSelect;
    }

    public static Parser getParseText() {
        return _parseText;
    }

    public static Parser getParseOptGroup() {
        return _parseOptGroup;
    }

    private static Parser _parseHTML     = new ParseHTML();
    private static Parser _parseHead     = new ParseHead();
    private static Parser _parseTitle    = new ParseTitle();
    private static Parser _parseScript   = new ParseScript();
    private static Parser _parseBody     = new ParseBody();
    private static Parser _parseFrameSet = new ParseFrameSet();
    private static Parser _parseInline   = new ParseInline();
    private static Parser _parseList     = new ParseList();
    private static Parser _parseDefList  = new ParseDefList();
    private static Parser _parsePre      = new ParsePre();
    private static Parser _parseBlock    = new ParseBlock();
    private static Parser _parseTableTag = new ParseTableTag();
    private static Parser _parseColGroup = new ParseColGroup();
    private static Parser _parseRowGroup = new ParseRowGroup();
    private static Parser _parseRow      = new ParseRow();
    private static Parser _parseNoFrames = new ParseNoFrames();
    private static Parser _parseSelect   = new ParseSelect();
    private static Parser _parseText     = new ParseText();
    private static Parser _parseOptGroup = new ParseOptGroup();

    /*
     * HTML is the top level element
     */
    public static Node parseDocument(Lexer lexer) {
        Node node, document, html;
        Node doctype = null;
        TagTable tt = lexer.configuration.tt;

        document = lexer.newNode();
        document.type = Node.RootNode;

        while (true) {
            node = lexer.getToken(Lexer.IgnoreWhitespace);
            if (node == null)
                break;

            /* deal with comments etc. */
            if (Node.insertMisc(document, node))
                continue;

            if (node.type == Node.DocTypeTag) {
                if (doctype == null) {
                    Node.insertNodeAtEnd(document, node);
                    doctype = node;
                } else
                    Report.warning(lexer, document, node, Report.DISCARDING_UNEXPECTED);
                continue;
            }

            if (node.type == Node.EndTag) {
                Report.warning(lexer, document, node, Report.DISCARDING_UNEXPECTED); //TODO?
                continue;
            }

            if (node.type != Node.StartTag || node.tag != tt.tagHtml) {
                lexer.ungetToken();
                html = lexer.inferredTag("html");
            } else
                html = node;

            Node.insertNodeAtEnd(document, html);
            getParseHTML().parse(lexer, html, (short) 0); // TODO?
            break;
        }

        return document;
    }

    /**
     * Indicates whether or not whitespace should be preserved for this element.
     * If an <code>xml:space</code> attribute is found, then if the attribute
     * value is <code>preserve</code>, returns <code>true</code>. For any other
     * value, returns <code>false</code>. If an <code>xml:space</code> attribute
     * was <em>not</em> found, then the following element names result in a
     * return value of <code>true:
     *  pre, script, style,</code> and <code>xsl:text</code>. Finally, if a
     * <code>TagTable</code> was passed in and the element appears as the "pre"
     * element in the <code>TagTable</code>, then <code>true</code> will be
     * returned. Otherwise, <code>false</code> is returned.
     * 
     * @param element The <code>Node</code> to test to see if whitespace should
     *            be preserved.
     * @param tt The <code>TagTable</code> to test for the
     *            <code>getNodePre()</code> function. This may be
     *            <code>null</code>, in which case this test is bypassed.
     * @return <code>true</code> or <code>false</code>, as explained above.
     */

    public static boolean XMLPreserveWhiteSpace(Node element, TagTable tt) {
        AttVal attribute;

        /* search attributes for xml:space */
        for (attribute = element.attributes; attribute != null; attribute = attribute.next) {
            if (attribute.attribute.equals("xml:space")) {
                if (attribute.value.equals("preserve"))
                    return true;

                return false;
            }
        }

        /* kludge for html docs without explicit xml:space attribute */
        if (Lexer.wstrcasecmp(element.element, "pre") == 0 || Lexer.wstrcasecmp(element.element, "script") == 0
                || Lexer.wstrcasecmp(element.element, "style") == 0)
            return true;

        if ((tt != null) && (tt.findParser(element) == getParsePre()))
            return true;

        /* kludge for XSL docs */
        if (Lexer.wstrcasecmp(element.element, "xsl:text") == 0)
            return true;

        return false;
    }

    /*
     * XML documents
     */
    public static void parseXMLElement(Lexer lexer, Node element, short mode) {
        Node node;

        /* Jeff Young's kludge for XSL docs */

        if (Lexer.wstrcasecmp(element.element, "xsl:text") == 0)
            return;

        /* if node is pre or has xml:space="preserve" then do so */

        if (XMLPreserveWhiteSpace(element, lexer.configuration.tt))
            mode = Lexer.Preformatted;

        while (true) {
            node = lexer.getToken(mode);
            if (node == null)
                break;
            if (node.type == Node.EndTag && node.element.equals(element.element)) {
                element.closed = true;
                break;
            }

            /* discard unexpected end tags */
            if (node.type == Node.EndTag) {
                Report.error(lexer, element, node, Report.UNEXPECTED_ENDTAG);
                continue;
            }

            /* parse content on seeing start tag */
            if (node.type == Node.StartTag)
                parseXMLElement(lexer, node, mode);

            Node.insertNodeAtEnd(element, node);
        }

        /*
         * if first child is text then trim initial space and delete text node
         * if it is empty.
         */

        node = element.content;

        if (node != null && node.type == Node.TextNode && mode != Lexer.Preformatted) {
            if (node.textarray[node.start] == (byte) ' ') {
                node.start++;

                if (node.start >= node.end)
                    Node.discardElement(node);
            }
        }

        /*
         * if last child is text then trim final space and delete the text node
         * if it is empty
         */

        node = element.last;

        if (node != null && node.type == Node.TextNode && mode != Lexer.Preformatted) {
            if (node.textarray[node.end - 1] == (byte) ' ') {
                node.end--;

                if (node.start >= node.end)
                    Node.discardElement(node);
            }
        }
    }

    public static Node parseXMLDocument(Lexer lexer) {
        Node node, document, doctype;

        document = lexer.newNode();
        document.type = Node.RootNode;
        doctype = null;
        lexer.configuration.XmlTags = true;

        while (true) {
            node = lexer.getToken(Lexer.IgnoreWhitespace);
            if (node == null)
                break;
            /* discard unexpected end tags */
            if (node.type == Node.EndTag) {
                Report.warning(lexer, null, node, Report.UNEXPECTED_ENDTAG);
                continue;
            }

            /* deal with comments etc. */
            if (Node.insertMisc(document, node))
                continue;

            if (node.type == Node.DocTypeTag) {
                if (doctype == null) {
                    Node.insertNodeAtEnd(document, node);
                    doctype = node;
                } else
                    Report.warning(lexer, document, node, Report.DISCARDING_UNEXPECTED); // TODO
                continue;
            }

            /* if start tag then parse element's content */
            if (node.type == Node.StartTag) {
                Node.insertNodeAtEnd(document, node);
                parseXMLElement(lexer, node, Lexer.IgnoreWhitespace);
            }

        }

        if (false) { //#if 0
            /* discard the document type */
            node = document.findDocType();

            if (node != null)
                Node.discardElement(node);
        } // #endif

        if (doctype != null && !lexer.checkDocTypeKeyWords(doctype))
            Report.warning(lexer, doctype, null, Report.DTYPE_NOT_UPPER_CASE);

        /* ensure presence of initial <?XML version="1.0"?> */
        if (lexer.configuration.XmlPi)
            lexer.fixXMLPI(document);

        return document;
    }

    public static boolean isJavaScript(Node node) {
        boolean result = false;
        AttVal attr;

        if (node.attributes == null)
            return true;

        for (attr = node.attributes; attr != null; attr = attr.next) {
            if ((Lexer.wstrcasecmp(attr.attribute, "language") == 0 || Lexer.wstrcasecmp(attr.attribute, "type") == 0)
                    && Lexer.wsubstr(attr.value, "javascript"))
                result = true;
        }

        return result;
    }

}
